#!/usr/bin/python3
# -*- coding: utf-8 -*
"""
Description: data controller.
Class: Datasource, FMADatasource
"""
import os
import shlex
import datetime
from subprocess import Popen, PIPE
from collections import defaultdict
from glob import glob
from socket import gethostname

from ..libs.log import LOGGER
from ..libs.readconfig import read_json_config_file
from ..libs.time_utils import time_limit
from .spec import SpecData


class Datasource():
    """
    Class Datasource reads the configuration file related to the log and then reads
    log according to the file.
    """

    def __init__(self, config_file):
        """
        Class instance initialization.

        Args:
            config_file (str): path to log configuration file.
        """
        self.config_file = config_file
        self.data = []
        self.param_config = defaultdict(dict)
        self.get_param_config()

    def get_param_config(self):
        """
        Read the contents of the log configuration file.

        Args:

        Returns:
            dict
        """

        param_config = read_json_config_file(self.config_file)

        if param_config is None:
            return

        for param_type in ('files', 'commands', 'globs'):
            if param_type in param_config.keys():
                for item in param_config[param_type]:
                    if 'symbolic_name' not in item.keys():
                        continue
                    if item['symbolic_name'] in self.param_config.keys():
                        LOGGER.warning(
                            "The name [%s] has been used, choose to ignore this item.",
                            item['symbolic_name'])
                        continue
                    self.param_config[item['symbolic_name']].update(item)

    def read_all_data(self, **kwargs):
        """
        Read all logs that configured in the configuration file.

        Args:

        Returns:
            dict
        """
        # init
        self.data = []

        if len(kwargs) > 0:
            LOGGER.warning("Don't need parameter.")

        hostname = gethostname()

        for key in self.param_config.keys():
            content_type = self.param_config[key].get('content_type')
            content, time, path = self.read_data(key)
            if isinstance(path, list):
                if len(content) != len(path):
                    continue
                for idx, one_path in enumerate(path):
                    spec = SpecData(file_path=one_path, content=content[idx],
                                    symbolic_name=key, time=time, hostname=hostname,
                                    content_type=content_type)
                    self.data.append(spec)
            else:
                spec = SpecData(file_path=path, content=content,
                                symbolic_name=key, time=time, hostname=hostname,
                                content_type=content_type)
                self.data.append(spec)

        return self.data

    def read_data(self, app_type):
        """
        Read logs by application type.

        Args:
            app_type (str): the application type of the log.
            is_filter (bool): whether to filter content according to including lines or
                              excluding lines.

        Returns:
            list or None: None when the log type is unspecified or there is no path or command.
        """

        data = None
        collect_time = None
        path = None
        sudo = False

        if app_type not in self.param_config.keys():
            LOGGER.warning("There is no type [%s]", app_type)
        else:
            authority = self.param_config[app_type].get('sudo')
            if authority == 'on':
                sudo = True
            log_type = self.param_config[app_type].get('log_type')
            # don't specify the log type, ignore it.
            if log_type is None:
                LOGGER.warning(
                    "Unspecified log type for [%s].", app_type)
            # get content from reading file
            if 'file' in self.param_config[app_type].keys():
                path = self.param_config[app_type]['file']
                data = self._read_file(path, sudo)
            # get content from command
            elif 'command' in self.param_config[app_type].keys():
                path = self.param_config[app_type]['command']
                data = self._shell_command(path, sudo)
            elif 'glob' in self.param_config[app_type].keys():
                data, path = self._glob(
                    self.param_config[app_type]['glob'], sudo)
            else:
                LOGGER.warning(
                    "Unable to read [%s] for it doesn't have path or cmd.", app_type)
            # need filtered?
            collect_time = datetime.datetime.now()

        return data, collect_time, path

    def _read_file(self, path, sudo):
        """
        Read file content according to the file path.

        Args:
            path (str): file path.
            sudo (bool): whether to use sudo.

        Returns:
            list or None
        """
        if not os.path.exists(path):
            LOGGER.error("'%s' doesn't exist.", path)
            return None

        command = 'cat ' + path
        return self._shell_command(command, sudo)

    @time_limit(2)
    def _shell_command(self, command='', sudo=False):
        """
        Execute shell command.

        Args:
            command (str): shell command.
            sudo (bool): whether to use sudo.

        Returns:
            list: ouput of the command.
        """
        if sudo:
            command = "sudo " + command

        cmd_list = self.cmd_format(command)

        try:
            for index, cmd in enumerate(cmd_list):
                if index == 0:
                    popen = Popen(cmd, stdout=PIPE)
                else:
                    popen = Popen(cmd, stdin=popen.stdout, stdout=PIPE)
        except FileNotFoundError:
            LOGGER.error("wrong command: %s.", command)
            return None

        try:
            stdout = popen.communicate()
            popen.poll()
            content = stdout[0].decode(encoding='utf-8')
        except UnicodeDecodeError as error:
            LOGGER.error(error)
            return None

        content = content.strip('\n')

        if len(content) == 0:
            return None

        return content.split('\n')

    def _glob(self, glob_path, sudo):
        # get cur file or dir in the path
        files = glob(glob_path)
        content = []
        paths = []
        path = []

        for file in files:
            if os.path.isdir(file):
                glog_object = os.walk(file)
                for path_name, _, file_list in glog_object:
                    for file_name in file_list:
                        paths.append(os.path.join(path_name, file_name))
            else:
                paths.append(file)

        for file in paths:
            tmp_content = self._read_file(file, sudo)
            content.append(tmp_content)
            path.append(file)

        return content, path

    @staticmethod
    def cmd_format(command):
        """
        transfer command(str) to command args(list)

        Args:
            command (str): raw command

        Returns:
            list
        """
        result = []
        cmd_list = command.split('|')

        for cmd in cmd_list:
            cmd_arg = shlex.split(cmd)
            result.append(cmd_arg)

        return result
